use cow_utils::CowUtils;
use oxc_ast::{
    AstKind,
    ast::{JSXAttributeItem, JSXOpeningElement},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{
    AstNode,
    context::LintContext,
    globals::{AriaProperty, VALID_ARIA_ROLES, is_valid_aria_property},
    rule::Rule,
    utils::{
        get_element_type, get_jsx_attribute_name, get_string_literal_prop_value,
        has_jsx_prop_ignore_case,
    },
};

declare_oxc_lint!(
    /// ### What it does
    ///
    /// Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. Many ARIA attributes (states and properties) can only be used on elements with particular roles. Some elements have implicit roles, such as `<a href="#" />`, which will resolve to `role="link"`.
    ///
    /// ### Examples
    ///
    /// Examples of **incorrect** code for this rule:
    /// ```jsx
    /// <ul role="radiogroup" "aria-labelledby"="foo">
    ///     <li aria-required tabIndex="-1" role="radio" aria-checked="false">Rainbow Trout</li>
    ///     <li aria-required tabIndex="-1" role="radio" aria-checked="false">Brook Trout</li>
    ///     <li aria-required tabIndex="0" role="radio" aria-checked="true">Lake Trout</li>
    /// </ul>
    /// ```
    ///
    /// Examples of **correct** code for this rule:
    /// ```jsx
    /// <ul role="radiogroup" aria-required "aria-labelledby"="foo">
    ///     <li tabIndex="-1" role="radio" aria-checked="false">Rainbow Trout</li>
    ///     <li tabIndex="-1" role="radio" aria-checked="false">Brook Trout</li>
    ///     <li tabIndex="0" role="radio" aria-checked="true">Lake Trout</li>
    /// </ul>
    /// ```
    RoleSupportsAriaProps,
    jsx_a11y,
    correctness
);

#[derive(Debug, Default, Clone)]
pub struct RoleSupportsAriaProps;

fn default(span: Span, attr_name: &str, role: &str) -> OxcDiagnostic {
    OxcDiagnostic::warn(format!("The attribute {attr_name} is not supported by the role {role}."))
        .with_help(format!("Try to remove invalid attribute {attr_name}."))
        .with_label(span)
}

fn is_implicit_diagnostic(span: Span, attr_name: &str, role: &str, el_name: &str) -> OxcDiagnostic {
    OxcDiagnostic::warn(format!("The attribute {attr_name} is not supported by the role {role}. This role is implicit on the element {el_name}."))
        .with_help(format!("Try to remove invalid attribute {attr_name}."))
        .with_label(span)
}

impl Rule for RoleSupportsAriaProps {
    fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
        let AstKind::JSXOpeningElement(jsx_el) = node.kind() else {
            return;
        };

        let el_type = get_element_type(ctx, jsx_el);

        let role = has_jsx_prop_ignore_case(jsx_el, "role");
        let role_value = role.map_or_else(
            || get_implicit_role(jsx_el, &el_type),
            |i| get_string_literal_prop_value(i),
        );
        let is_implicit = role_value.is_some() && role.is_none();
        if let Some(role_value) = role_value {
            if !VALID_ARIA_ROLES.contains(role_value) {
                return;
            }
            for attr in &jsx_el.attributes {
                if let JSXAttributeItem::Attribute(attr) = attr {
                    let name = get_jsx_attribute_name(&attr.name);
                    let name = name.cow_to_ascii_lowercase();
                    if is_valid_aria_property(&name)
                        && !is_valid_aria_property_for_role(role_value, &name)
                    {
                        ctx.diagnostic(if is_implicit {
                            is_implicit_diagnostic(attr.span, &name, role_value, &el_type)
                        } else {
                            default(attr.span, &name, role_value)
                        });
                    }
                }
            }
        }
    }
}

/// ref: <https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/v6.9.0/src/util/getImplicitRole.js>
fn get_implicit_role<'a>(
    node: &'a JSXOpeningElement<'a>,
    element_type: &str,
) -> Option<&'static str> {
    let implicit_role = match element_type {
        "a" | "area" | "link" => match has_jsx_prop_ignore_case(node, "href") {
            Some(_) => "link",
            None => "",
        },
        "article" => "article",
        "aside" => "complementary",
        "body" => "document",
        "button" => "button",
        "datalist" | "select" => "listbox",
        "details" => "group",
        "dialog" => "dialog",
        "form" => "form",
        "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => "heading",
        "hr" => "separator",
        "img" => has_jsx_prop_ignore_case(node, "alt").map_or("img", |i| {
            get_string_literal_prop_value(i)
                .map_or("img", |v| if v.is_empty() { "" } else { "img" })
        }),
        "input" => has_jsx_prop_ignore_case(node, "type").map_or("textbox", |input_type| {
            match get_string_literal_prop_value(input_type) {
                Some("button" | "image" | "reset" | "submit") => "button",
                Some("checkbox") => "checkbox",
                Some("radio") => "radio",
                Some("range") => "slider",
                _ => "textbox",
            }
        }),
        "li" => "listitem",
        "menu" => has_jsx_prop_ignore_case(node, "type").map_or("", |v| {
            get_string_literal_prop_value(v)
                .map_or("", |v| if v == "toolbar" { "toolbar" } else { "" })
        }),
        "menuitem" => has_jsx_prop_ignore_case(node, "type").map_or("", |v| {
            match get_string_literal_prop_value(v) {
                Some("checkbox") => "menuitemcheckbox",
                Some("command") => "menuitem",
                Some("radio") => "menuitemradio",
                _ => "",
            }
        }),
        "meter" | "progress" => "progressbar",
        "nav" => "navigation",
        "ol" | "ul" => "list",
        "option" => "option",
        "output" => "status",
        "section" => "region",
        "tbody" | "tfoot" | "thead" => "rowgroup",
        "textarea" => "textbox",
        _ => "",
    };

    VALID_ARIA_ROLES.contains(implicit_role).then_some(implicit_role)
}

const ALERT_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const ALERTDIALOG_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Modal,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const APPLICATION_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const ARTICLE_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::PosInSet,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::SetSize,
];

const BUTTON_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Pressed,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const CAPTION_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const CELL_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::ColIndex,
    AriaProperty::ColSpan,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::RowIndex,
    AriaProperty::RowSpan,
];

const CHECKBOX_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Checked,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
];

const COLUMN_HEADER_ROW_HEADER_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::ColIndex,
    AriaProperty::ColSpan,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
    AriaProperty::RowIndex,
    AriaProperty::RowSpan,
    AriaProperty::Selected,
    AriaProperty::Sort,
];

const COMBOBOX_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::AutoComplete,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
];

const COMPOSITE_GROUP_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const CONTENTINFO_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const DOC_ABSTRACT_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const DOC_BIBLOENTRY_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Level,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::PosInSet,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::SetSize,
];

const DOC_BIBLIOREF_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const DOC_PAGE_BREAK_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Orientation,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const GRID_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::ColCount,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Multiselectable,
    AriaProperty::Owns,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::RowCount,
];

const GRIDCELL_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::ColIndex,
    AriaProperty::ColSpan,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
    AriaProperty::RowIndex,
    AriaProperty::RowSpan,
    AriaProperty::Selected,
];

const HEADING_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Level,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const INPUT_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const INSERTION_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const LINK_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const LISTBOX_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Multiselectable,
    AriaProperty::Orientation,
    AriaProperty::Owns,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
];

const LISTITEM_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Level,
    AriaProperty::Live,
    AriaProperty::PosInSet,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::SetSize,
];

const MARK_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::BrailleLabel,
    AriaProperty::BrailleRoleDescription,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Description,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const MENU_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Orientation,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const MENUITEM_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::PosInSet,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::SetSize,
];

const MENUITEMCHECKBOX_ETC_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Checked,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::PosInSet,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
    AriaProperty::SetSize,
];

const METER_PROGRESSBAR_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::ValueMax,
    AriaProperty::ValueMin,
    AriaProperty::ValueNow,
    AriaProperty::ValueText,
];

const OPTION_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Checked,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::Selected,
    AriaProperty::SetSize,
];

const RADIO_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Checked,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::PosInSet,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::SetSize,
];

const RADIOGROUP_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Orientation,
    AriaProperty::Owns,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
];

const RANGE_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::ValueMax,
    AriaProperty::ValueMin,
    AriaProperty::ValueNow,
];

const ROW_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::ColIndex,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Level,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::PosInSet,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::RowIndex,
    AriaProperty::Selected,
    AriaProperty::SetSize,
];

const SCROLLBAR_SEPARATOR_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Orientation,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::ValueMax,
    AriaProperty::ValueMin,
    AriaProperty::ValueNow,
    AriaProperty::ValueText,
];

const SEARCHBOX_TEXTBOX_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::AutoComplete,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Multiline,
    AriaProperty::Owns,
    AriaProperty::Placeholder,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
];

const SLIDER_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Orientation,
    AriaProperty::Owns,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::ValueMin,
    AriaProperty::ValueMax,
    AriaProperty::ValueNow,
    AriaProperty::ValueText,
];

const SPINBUTTON_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
    AriaProperty::ValueMax,
    AriaProperty::ValueMin,
    AriaProperty::ValueNow,
    AriaProperty::ValueText,
];

const TAB_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::PosInSet,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::Selected,
    AriaProperty::SetSize,
];

const TABLE_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::ColCount,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::RowCount,
];

const TABLIST_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Level,
    AriaProperty::Live,
    AriaProperty::Multiselectable,
    AriaProperty::Orientation,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
];

const TREE_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::ErrorMessage,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Multiselectable,
    AriaProperty::Orientation,
    AriaProperty::Owns,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
];

const TREEGRID_PROPS: &[AriaProperty] = &[
    AriaProperty::ActiveDescendant,
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::ColCount,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::Hidden,
    AriaProperty::Invalid,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Live,
    AriaProperty::Multiselectable,
    AriaProperty::Orientation,
    AriaProperty::Owns,
    AriaProperty::Readonly,
    AriaProperty::Relevant,
    AriaProperty::Required,
    AriaProperty::RoleDescription,
    AriaProperty::RowCount,
];

const TREEITEM_PROPS: &[AriaProperty] = &[
    AriaProperty::Atomic,
    AriaProperty::Busy,
    AriaProperty::Checked,
    AriaProperty::Controls,
    AriaProperty::Current,
    AriaProperty::DescribedBy,
    AriaProperty::Details,
    AriaProperty::Disabled,
    AriaProperty::DropEffect,
    AriaProperty::Expanded,
    AriaProperty::FlowTo,
    AriaProperty::Grabbed,
    AriaProperty::HasPopup,
    AriaProperty::Hidden,
    AriaProperty::KeyShortcuts,
    AriaProperty::Label,
    AriaProperty::LabelledBy,
    AriaProperty::Level,
    AriaProperty::Live,
    AriaProperty::Owns,
    AriaProperty::PosInSet,
    AriaProperty::Relevant,
    AriaProperty::RoleDescription,
    AriaProperty::Selected,
    AriaProperty::SetSize,
];

fn is_valid_aria_property_for_role(role_value: &str, aria_property: &str) -> bool {
    let Ok(aria_property) = AriaProperty::try_from(aria_property) else {
        return false;
    };
    // ref: https://github.com/A11yance/aria-query/blob/fff6f07c714e8048f4fe084cec74f24248e5673d/scripts/roles.json
    match role_value {
        "alert" | "banner" | "blockquote" | "command" | "complementary" => {
            ALERT_ETC_PROPS.contains(&aria_property)
        }
        "alertdialog" | "dialog" | "window" => ALERTDIALOG_ETC_PROPS.contains(&aria_property),
        "application" | "graphics-object" => APPLICATION_ETC_PROPS.contains(&aria_property),
        "article" => ARTICLE_PROPS.contains(&aria_property),
        "button" => BUTTON_PROPS.contains(&aria_property),
        "caption" | "code" | "deletion" | "emphasis" | "generic" => {
            CAPTION_ETC_PROPS.contains(&aria_property)
        }
        "cell" => CELL_PROPS.contains(&aria_property),
        "checkbox" | "switch" => CHECKBOX_PROPS.contains(&aria_property),
        "columnheader" | "rowheader" => COLUMN_HEADER_ROW_HEADER_PROPS.contains(&aria_property),
        "combobox" => COMBOBOX_PROPS.contains(&aria_property),
        "composite" | "group" => COMPOSITE_GROUP_PROPS.contains(&aria_property),
        "contentinfo" | "definition" | "directory" | "document" | "feed" | "figure" | "form"
        | "img" | "landmark" | "list" | "log" | "main" | "marquee" | "math" | "navigation"
        | "note" | "region" | "roletype" | "rowgroup" | "search" | "section" | "sectionhead"
        | "status" | "structure" | "tabpanel" | "term" | "time" | "timer" | "tooltip"
        | "widget" => CONTENTINFO_ETC_PROPS.contains(&aria_property),
        "doc-abstract"
        | "doc-acknowledgments"
        | "doc-afterword"
        | "doc-appendix"
        | "doc-backlink"
        | "doc-bibliography" => DOC_ABSTRACT_ETC_PROPS.contains(&aria_property),
        "doc-biblioentry" | "doc-endnote" => DOC_BIBLOENTRY_ETC_PROPS.contains(&aria_property),
        "doc-biblioref" | "doc-chapter" | "doc-colophon" | "doc-conclusion" | "doc-cover"
        | "doc-credit" | "doc-credits" | "doc-dedication" | "doc-endnotes" | "doc-epigraph"
        | "doc-epilogue" | "doc-errata" | "doc-example" | "doc-footnote" | "doc-foreword"
        | "doc-glossary" | "doc-glossref" | "doc-index" | "doc-introduction" | "doc-noteref"
        | "doc-notice" | "doc-pagelist" | "doc-part" | "doc-preface" | "doc-prologue"
        | "doc-qna" | "doc-subtitle" | "doc-tip" | "doc-toc" | "graphics-document"
        | "graphics-symbol" => DOC_BIBLIOREF_ETC_PROPS.contains(&aria_property),
        "doc-pagebreak" => DOC_PAGE_BREAK_PROPS.contains(&aria_property),
        "doc-pullquote" | "none" => false,
        "grid" => GRID_PROPS.contains(&aria_property),
        "gridcell" => GRIDCELL_PROPS.contains(&aria_property),
        "heading" => HEADING_PROPS.contains(&aria_property),
        "input" => INPUT_PROPS.contains(&aria_property),
        "insertion" | "paragraph" | "presentation" | "strong" | "subscript" | "superscript" => {
            INSERTION_ETC_PROPS.contains(&aria_property)
        }
        "link" => LINK_PROPS.contains(&aria_property),
        "listbox" => LISTBOX_PROPS.contains(&aria_property),
        "listitem" => LISTITEM_PROPS.contains(&aria_property),
        "mark" => MARK_PROPS.contains(&aria_property),
        "menu" | "menubar" | "select" | "toolbar" => MENU_ETC_PROPS.contains(&aria_property),
        "menuitem" => MENUITEM_PROPS.contains(&aria_property),
        "menuitemcheckbox" | "menuitemradio" => MENUITEMCHECKBOX_ETC_PROPS.contains(&aria_property),
        "meter" | "progressbar" => METER_PROGRESSBAR_PROPS.contains(&aria_property),
        "option" => OPTION_PROPS.contains(&aria_property),
        "radio" => RADIO_PROPS.contains(&aria_property),
        "radiogroup" => RADIOGROUP_PROPS.contains(&aria_property),
        "range" => RANGE_PROPS.contains(&aria_property),
        "row" => ROW_PROPS.contains(&aria_property),
        "scrollbar" | "separator" => SCROLLBAR_SEPARATOR_PROPS.contains(&aria_property),
        "searchbox" | "textbox" => SEARCHBOX_TEXTBOX_PROPS.contains(&aria_property),
        "slider" => SLIDER_PROPS.contains(&aria_property),
        "spinbutton" => SPINBUTTON_PROPS.contains(&aria_property),
        "tab" => TAB_PROPS.contains(&aria_property),
        "table" => TABLE_PROPS.contains(&aria_property),
        "tablist" => TABLIST_PROPS.contains(&aria_property),
        "tree" => TREE_PROPS.contains(&aria_property),
        "treegrid" => TREEGRID_PROPS.contains(&aria_property),
        "treeitem" => TREEITEM_PROPS.contains(&aria_property),
        _ => unreachable!("role value is not valid"),
    }
}

#[test]
fn test() {
    use crate::tester::Tester;

    fn settings() -> serde_json::Value {
        serde_json::json!({
            "settings": { "jsx-a11y": {
                "components": {
                    "Link": "a"
                }
            } }
        })
    }

    let pass = vec![
        (r"<Foo bar />", None, None),
        (r"<div />", None, None),
        (r#"<div id="main" />"#, None, None),
        (r"<div role />", None, None),
        (r#"<div role="presentation" {...props} />"#, None, None),
        (r"<Foo.Bar baz={true} />", None, None),
        (r#"<Link href="/" aria-checked />"#, None, None),
        (r#"<a href="/" aria-expanded />"#, None, None),
        (r#"<a href="/" aria-atomic />"#, None, None),
        (r#"<a href="/" aria-busy />"#, None, None),
        (r#"<a href="/" aria-controls />"#, None, None),
        (r#"<a href="/" aria-current />"#, None, None),
        (r#"<a href="/" aria-describedby />"#, None, None),
        (r#"<a href="/" aria-disabled />"#, None, None),
        (r#"<a href="/" aria-dropeffect />"#, None, None),
        (r#"<a href="/" aria-flowto />"#, None, None),
        (r#"<a href="/" aria-haspopup />"#, None, None),
        (r#"<a href="/" aria-grabbed />"#, None, None),
        (r#"<a href="/" aria-hidden />"#, None, None),
        (r#"<a href="/" aria-label />"#, None, None),
        (r#"<a href="/" aria-labelledby />"#, None, None),
        (r#"<a href="/" aria-live />"#, None, None),
        (r#"<a href="/" aria-owns />"#, None, None),
        (r#"<a href="/" aria-relevant />"#, None, None),
        (r"<a aria-checked />", None, None),
        (r#"<area href="/" aria-expanded />"#, None, None),
        (r#"<area href="/" aria-atomic />"#, None, None),
        (r#"<area href="/" aria-busy />"#, None, None),
        (r#"<area href="/" aria-controls />"#, None, None),
        (r#"<area href="/" aria-describedby />"#, None, None),
        (r#"<area href="/" aria-disabled />"#, None, None),
        (r#"<area href="/" aria-dropeffect />"#, None, None),
        (r#"<area href="/" aria-flowto />"#, None, None),
        (r#"<area href="/" aria-grabbed />"#, None, None),
        (r#"<area href="/" aria-haspopup />"#, None, None),
        (r#"<area href="/" aria-hidden />"#, None, None),
        (r#"<area href="/" aria-label />"#, None, None),
        (r#"<area href="/" aria-labelledby />"#, None, None),
        (r#"<area href="/" aria-live />"#, None, None),
        (r#"<area href="/" aria-owns />"#, None, None),
        (r#"<area href="/" aria-relevant />"#, None, None),
        (r"<area aria-checked />", None, None),
        (r#"<link href="/" aria-expanded />"#, None, None),
        (r#"<link href="/" aria-atomic />"#, None, None),
        (r#"<link href="/" aria-busy />"#, None, None),
        (r#"<link href="/" aria-controls />"#, None, None),
        (r#"<link href="/" aria-describedby />"#, None, None),
        (r#"<link href="/" aria-disabled />"#, None, None),
        (r#"<link href="/" aria-dropeffect />"#, None, None),
        (r#"<link href="/" aria-flowto />"#, None, None),
        (r#"<link href="/" aria-grabbed />"#, None, None),
        (r#"<link href="/" aria-haspopup />"#, None, None),
        (r#"<link href="/" aria-hidden />"#, None, None),
        (r#"<link href="/" aria-label />"#, None, None),
        (r#"<link href="/" aria-labelledby />"#, None, None),
        (r#"<link href="/" aria-live />"#, None, None),
        (r#"<link href="/" aria-owns />"#, None, None),
        (r#"<link href="/" aria-relevant />"#, None, None),
        (r"<link aria-checked />", None, None),
        (r#"<img alt="" aria-checked />"#, None, None),
        (r#"<img alt="foobar" aria-busy />"#, None, None),
        (r#"<menu type="toolbar" aria-activedescendant />"#, None, None),
        (r#"<menu type="toolbar" aria-atomic />"#, None, None),
        (r#"<menu type="toolbar" aria-busy />"#, None, None),
        (r#"<menu type="toolbar" aria-controls />"#, None, None),
        (r#"<menu type="toolbar" aria-describedby />"#, None, None),
        (r#"<menu type="toolbar" aria-disabled />"#, None, None),
        (r#"<menu type="toolbar" aria-dropeffect />"#, None, None),
        (r#"<menu type="toolbar" aria-flowto />"#, None, None),
        (r#"<menu type="toolbar" aria-grabbed />"#, None, None),
        (r#"<menu type="toolbar" aria-hidden />"#, None, None),
        (r#"<menu type="toolbar" aria-label />"#, None, None),
        (r#"<menu type="toolbar" aria-labelledby />"#, None, None),
        (r#"<menu type="toolbar" aria-live />"#, None, None),
        (r#"<menu type="toolbar" aria-owns />"#, None, None),
        (r#"<menu type="toolbar" aria-relevant />"#, None, None),
        (r"<menu aria-checked />", None, None),
        (r#"<menuitem type="command" aria-atomic />"#, None, None),
        (r#"<menuitem type="command" aria-busy />"#, None, None),
        (r#"<menuitem type="command" aria-controls />"#, None, None),
        (r#"<menuitem type="command" aria-describedby />"#, None, None),
        (r#"<menuitem type="command" aria-disabled />"#, None, None),
        (r#"<menuitem type="command" aria-dropeffect />"#, None, None),
        (r#"<menuitem type="command" aria-flowto />"#, None, None),
        (r#"<menuitem type="command" aria-grabbed />"#, None, None),
        (r#"<menuitem type="command" aria-hidden />"#, None, None),
        (r#"<menuitem type="command" aria-label />"#, None, None),
        (r#"<menuitem type="command" aria-labelledby />"#, None, None),
        (r#"<menuitem type="command" aria-live />"#, None, None),
        (r#"<menuitem type="command" aria-owns />"#, None, None),
        (r#"<menuitem type="command" aria-relevant />"#, None, None),
        (r#"<menuitem type="checkbox" aria-checked />"#, None, None),
        (r#"<menuitem type="checkbox" aria-atomic />"#, None, None),
        (r#"<menuitem type="checkbox" aria-busy />"#, None, None),
        (r#"<menuitem type="checkbox" aria-controls />"#, None, None),
        (r#"<menuitem type="checkbox" aria-describedby />"#, None, None),
        (r#"<menuitem type="checkbox" aria-disabled />"#, None, None),
        (r#"<menuitem type="checkbox" aria-dropeffect />"#, None, None),
        (r#"<menuitem type="checkbox" aria-flowto />"#, None, None),
        (r#"<menuitem type="checkbox" aria-grabbed />"#, None, None),
        (r#"<menuitem type="checkbox" aria-hidden />"#, None, None),
        (r#"<menuitem type="checkbox" aria-invalid />"#, None, None),
        (r#"<menuitem type="checkbox" aria-label />"#, None, None),
        (r#"<menuitem type="checkbox" aria-labelledby />"#, None, None),
        (r#"<menuitem type="checkbox" aria-live />"#, None, None),
        (r#"<menuitem type="checkbox" aria-owns />"#, None, None),
        (r#"<menuitem type="checkbox" aria-relevant />"#, None, None),
        (r#"<menuitem type="radio" aria-checked />"#, None, None),
        (r#"<menuitem type="radio" aria-atomic />"#, None, None),
        (r#"<menuitem type="radio" aria-busy />"#, None, None),
        (r#"<menuitem type="radio" aria-controls />"#, None, None),
        (r#"<menuitem type="radio" aria-describedby />"#, None, None),
        (r#"<menuitem type="radio" aria-disabled />"#, None, None),
        (r#"<menuitem type="radio" aria-dropeffect />"#, None, None),
        (r#"<menuitem type="radio" aria-flowto />"#, None, None),
        (r#"<menuitem type="radio" aria-grabbed />"#, None, None),
        (r#"<menuitem type="radio" aria-hidden />"#, None, None),
        (r#"<menuitem type="radio" aria-invalid />"#, None, None),
        (r#"<menuitem type="radio" aria-label />"#, None, None),
        (r#"<menuitem type="radio" aria-labelledby />"#, None, None),
        (r#"<menuitem type="radio" aria-live />"#, None, None),
        (r#"<menuitem type="radio" aria-owns />"#, None, None),
        (r#"<menuitem type="radio" aria-relevant />"#, None, None),
        (r#"<menuitem type="radio" aria-posinset />"#, None, None),
        (r#"<menuitem type="radio" aria-setsize />"#, None, None),
        (r"<menuitem aria-checked />", None, None),
        (r#"<menuitem type="foobar" aria-checked />"#, None, None),
        (r#"<input type="button" aria-expanded />"#, None, None),
        (r#"<input type="button" aria-pressed />"#, None, None),
        (r#"<input type="button" aria-atomic />"#, None, None),
        (r#"<input type="button" aria-busy />"#, None, None),
        (r#"<input type="button" aria-controls />"#, None, None),
        (r#"<input type="button" aria-describedby />"#, None, None),
        (r#"<input type="button" aria-disabled />"#, None, None),
        (r#"<input type="button" aria-dropeffect />"#, None, None),
        (r#"<input type="button" aria-flowto />"#, None, None),
        (r#"<input type="button" aria-grabbed />"#, None, None),
        (r#"<input type="button" aria-hidden />"#, None, None),
        (r#"<input type="button" aria-label />"#, None, None),
        (r#"<input type="button" aria-labelledby />"#, None, None),
        (r#"<input type="button" aria-live />"#, None, None),
        (r#"<input type="button" aria-owns />"#, None, None),
        (r#"<input type="button" aria-relevant />"#, None, None),
        (r#"<input type="image" aria-expanded />"#, None, None),
        (r#"<input type="image" aria-pressed />"#, None, None),
        (r#"<input type="image" aria-atomic />"#, None, None),
        (r#"<input type="image" aria-busy />"#, None, None),
        (r#"<input type="image" aria-controls />"#, None, None),
        (r#"<input type="image" aria-describedby />"#, None, None),
        (r#"<input type="image" aria-disabled />"#, None, None),
        (r#"<input type="image" aria-dropeffect />"#, None, None),
        (r#"<input type="image" aria-flowto />"#, None, None),
        (r#"<input type="image" aria-grabbed />"#, None, None),
        (r#"<input type="image" aria-haspopup />"#, None, None),
        (r#"<input type="image" aria-hidden />"#, None, None),
        (r#"<input type="image" aria-label />"#, None, None),
        (r#"<input type="image" aria-labelledby />"#, None, None),
        (r#"<input type="image" aria-live />"#, None, None),
        (r#"<input type="image" aria-owns />"#, None, None),
        (r#"<input type="image" aria-relevant />"#, None, None),
        (r#"<input type="reset" aria-expanded />"#, None, None),
        (r#"<input type="reset" aria-pressed />"#, None, None),
        (r#"<input type="reset" aria-atomic />"#, None, None),
        (r#"<input type="reset" aria-busy />"#, None, None),
        (r#"<input type="reset" aria-controls />"#, None, None),
        (r#"<input type="reset" aria-describedby />"#, None, None),
        (r#"<input type="reset" aria-disabled />"#, None, None),
        (r#"<input type="reset" aria-dropeffect />"#, None, None),
        (r#"<input type="reset" aria-flowto />"#, None, None),
        (r#"<input type="reset" aria-grabbed />"#, None, None),
        (r#"<input type="reset" aria-haspopup />"#, None, None),
        (r#"<input type="reset" aria-hidden />"#, None, None),
        (r#"<input type="reset" aria-label />"#, None, None),
        (r#"<input type="reset" aria-labelledby />"#, None, None),
        (r#"<input type="reset" aria-live />"#, None, None),
        (r#"<input type="reset" aria-owns />"#, None, None),
        (r#"<input type="reset" aria-relevant />"#, None, None),
        (r#"<input type="submit" aria-expanded />"#, None, None),
        (r#"<input type="submit" aria-pressed />"#, None, None),
        (r#"<input type="submit" aria-atomic />"#, None, None),
        (r#"<input type="submit" aria-busy />"#, None, None),
        (r#"<input type="submit" aria-controls />"#, None, None),
        (r#"<input type="submit" aria-describedby />"#, None, None),
        (r#"<input type="submit" aria-disabled />"#, None, None),
        (r#"<input type="submit" aria-dropeffect />"#, None, None),
        (r#"<input type="submit" aria-flowto />"#, None, None),
        (r#"<input type="submit" aria-grabbed />"#, None, None),
        (r#"<input type="submit" aria-haspopup />"#, None, None),
        (r#"<input type="submit" aria-hidden />"#, None, None),
        (r#"<input type="submit" aria-label />"#, None, None),
        (r#"<input type="submit" aria-labelledby />"#, None, None),
        (r#"<input type="submit" aria-live />"#, None, None),
        (r#"<input type="submit" aria-owns />"#, None, None),
        (r#"<input type="submit" aria-relevant />"#, None, None),
        (r#"<input type="checkbox" aria-atomic />"#, None, None),
        (r#"<input type="checkbox" aria-busy />"#, None, None),
        (r#"<input type="checkbox" aria-checked />"#, None, None),
        (r#"<input type="checkbox" aria-controls />"#, None, None),
        (r#"<input type="checkbox" aria-describedby />"#, None, None),
        (r#"<input type="checkbox" aria-disabled />"#, None, None),
        (r#"<input type="checkbox" aria-dropeffect />"#, None, None),
        (r#"<input type="checkbox" aria-flowto />"#, None, None),
        (r#"<input type="checkbox" aria-grabbed />"#, None, None),
        (r#"<input type="checkbox" aria-hidden />"#, None, None),
        (r#"<input type="checkbox" aria-invalid />"#, None, None),
        (r#"<input type="checkbox" aria-label />"#, None, None),
        (r#"<input type="checkbox" aria-labelledby />"#, None, None),
        (r#"<input type="checkbox" aria-live />"#, None, None),
        (r#"<input type="checkbox" aria-owns />"#, None, None),
        (r#"<input type="checkbox" aria-relevant />"#, None, None),
        (r#"<input type="radio" aria-atomic />"#, None, None),
        (r#"<input type="radio" aria-busy />"#, None, None),
        (r#"<input type="radio" aria-checked />"#, None, None),
        (r#"<input type="radio" aria-controls />"#, None, None),
        (r#"<input type="radio" aria-describedby />"#, None, None),
        (r#"<input type="radio" aria-disabled />"#, None, None),
        (r#"<input type="radio" aria-dropeffect />"#, None, None),
        (r#"<input type="radio" aria-flowto />"#, None, None),
        (r#"<input type="radio" aria-grabbed />"#, None, None),
        (r#"<input type="radio" aria-hidden />"#, None, None),
        (r#"<input type="radio" aria-label />"#, None, None),
        (r#"<input type="radio" aria-labelledby />"#, None, None),
        (r#"<input type="radio" aria-live />"#, None, None),
        (r#"<input type="radio" aria-owns />"#, None, None),
        (r#"<input type="radio" aria-relevant />"#, None, None),
        (r#"<input type="radio" aria-posinset />"#, None, None),
        (r#"<input type="radio" aria-setsize />"#, None, None),
        (r#"<input type="range" aria-valuemax />"#, None, None),
        (r#"<input type="range" aria-valuemin />"#, None, None),
        (r#"<input type="range" aria-valuenow />"#, None, None),
        (r#"<input type="range" aria-orientation />"#, None, None),
        (r#"<input type="range" aria-atomic />"#, None, None),
        (r#"<input type="range" aria-busy />"#, None, None),
        (r#"<input type="range" aria-controls />"#, None, None),
        (r#"<input type="range" aria-describedby />"#, None, None),
        (r#"<input type="range" aria-disabled />"#, None, None),
        (r#"<input type="range" aria-dropeffect />"#, None, None),
        (r#"<input type="range" aria-flowto />"#, None, None),
        (r#"<input type="range" aria-grabbed />"#, None, None),
        (r#"<input type="range" aria-haspopup />"#, None, None),
        (r#"<input type="range" aria-hidden />"#, None, None),
        (r#"<input type="range" aria-invalid />"#, None, None),
        (r#"<input type="range" aria-label />"#, None, None),
        (r#"<input type="range" aria-labelledby />"#, None, None),
        (r#"<input type="range" aria-live />"#, None, None),
        (r#"<input type="range" aria-owns />"#, None, None),
        (r#"<input type="range" aria-relevant />"#, None, None),
        (r#"<input type="email" aria-disabled />"#, None, None),
        (r#"<input type="password" aria-disabled />"#, None, None),
        (r#"<input type="search" aria-disabled />"#, None, None),
        (r#"<input type="tel" aria-disabled />"#, None, None),
        (r#"<input type="url" aria-disabled />"#, None, None),
        (r"<input aria-disabled />", None, None),
        // TODO: This should pass, but it doesn't. Because the current code does not determine if the attribute value is null, undefined, etc.
        //(r#"<h2 role="presentation" aria-level={null} />"#, None, None),
        //(r#"<h2 role="presentation" aria-level={undefined} />"#, None, None),
        (r"<button aria-pressed />", None, None),
        (r"<form aria-hidden />", None, None),
        (r"<h1 aria-hidden />", None, None),
        (r"<h2 aria-hidden />", None, None),
        (r"<h3 aria-hidden />", None, None),
        (r"<h4 aria-hidden />", None, None),
        (r"<h5 aria-hidden />", None, None),
        (r"<h6 aria-hidden />", None, None),
        (r"<hr aria-hidden />", None, None),
        (r"<li aria-current />", None, None),
        (r"<meter aria-atomic />", None, None),
        (r"<option aria-atomic />", None, None),
        (r"<progress aria-atomic />", None, None),
        (r"<textarea aria-hidden />", None, None),
        (r"<select aria-expanded />", None, None),
        (r"<datalist aria-expanded />", None, None),
        (r#"<div role="heading" aria-level />"#, None, None),
        (r#"<div role="heading" aria-level="1" />"#, None, None),
    ];

    let fail = vec![
        (r#"<a href="/" aria-checked />"#, None, None),
        (r#"<area href="/" aria-checked />"#, None, None),
        (r#"<link href="/" aria-checked />"#, None, None),
        (r#"<img alt="foobar" aria-checked />"#, None, None),
        (r#"<menu type="toolbar" aria-checked />"#, None, None),
        (r"<aside aria-checked />", None, None),
        (r"<ul aria-expanded />", None, None),
        (r"<details aria-expanded />", None, None),
        (r"<dialog aria-expanded />", None, None),
        (r"<aside aria-expanded />", None, None),
        (r"<article aria-expanded />", None, None),
        (r"<body aria-expanded />", None, None),
        (r"<li aria-expanded />", None, None),
        (r"<nav aria-expanded />", None, None),
        (r"<ol aria-expanded />", None, None),
        (r"<output aria-expanded />", None, None),
        (r"<section aria-expanded />", None, None),
        (r"<tbody aria-expanded />", None, None),
        (r"<tfoot aria-expanded />", None, None),
        (r"<thead aria-expanded />", None, None),
        (r#"<input type="radio" aria-invalid />"#, None, None),
        (r#"<input type="radio" aria-selected />"#, None, None),
        (r#"<input type="radio" aria-haspopup />"#, None, None),
        (r#"<input type="checkbox" aria-haspopup />"#, None, None),
        (r#"<input type="reset" aria-invalid />"#, None, None),
        (r#"<input type="image" aria-invalid />"#, None, None),
        (r#"<input type="button" aria-invalid />"#, None, None),
        (r#"<menuitem type="command" aria-invalid />"#, None, None),
        (r#"<menuitem type="radio" aria-selected />"#, None, None),
        (r#"<menu type="toolbar" aria-haspopup />"#, None, None),
        (r#"<menu type="toolbar" aria-invalid />"#, None, None),
        (r#"<menu type="toolbar" aria-expanded />"#, None, None),
        (r#"<link href="/" aria-invalid />"#, None, None),
        (r#"<area href="/" aria-invalid />"#, None, None),
        (r#"<a href="/" aria-invalid />"#, None, None),
        (r#"<Link href="/" aria-checked />"#, None, Some(settings())),
    ];

    Tester::new(RoleSupportsAriaProps::NAME, RoleSupportsAriaProps::PLUGIN, pass, fail)
        .test_and_snapshot();
}
